﻿using System;
using Silver3D.Math;

namespace Silver3D.Graphics
{
    public class SoftwareRenderer
    {
        public RenderContext RC { get; set; }

        private int[] cachedPixels;

        public SoftwareRenderer()
        {
        }

        private void DrawWuPixel(int offset, int color)
        {
            cachedPixels[offset] = color;
        }

        /// <summary>
        /// Adapted for BGR32 from listing found at:
        /// https://mail.cs.pub.ro/~mugurel.andreica/task_archive/Romania%20-%20Olympiads/Practice/CNCV%20-%20Romania%201997-2001/Old%20Progs%20-%20all%20stuff/CPP/GRAPHPRG/GP-JUN92.ASC
        /// and corresponding article:
        /// http://www.gamedev.net/reference/articles/article382.asp
        /// </summary>
        /// <param name="x0"></param>
        /// <param name="y0"></param>
        /// <param name="x1"></param>
        /// <param name="y1"></param>
        /// <param name="color"></param>
        private void DrawWuLine(int x0, int y0, int x1, int y1, ColorBgr32 color)
        {
            uint ErrorAdj, ErrorAcc, ErrorAccTemp, Weighting;
            int IntensityShift, DeltaX, DeltaY, Temp, XDir;
            int BaseColor = color.RGB;

            // we use overflows here
            unchecked
            {

                // ensure top to bottom by swapping points
                if (y0 > y1)
                {
                    Temp = y0; y0 = y1; y1 = Temp;
                    Temp = x0; x0 = x1; x1 = Temp;
                }

                // use this offset, dependent on x0 and y0, to draw pixels and save multiplications
                int stride = RC.FrameBuffer.PixelWidth;
                int offset = y0 * stride + x0;

                // draw initial pixel
                DrawWuPixel(offset, BaseColor);

                if ((DeltaX = x1 - x0) >= 0)
                {
                    XDir = 1;
                }
                else
                {
                    XDir = -1;
                    DeltaX = -DeltaX;
                }

                // horizontal line special case
                if ((DeltaY = y1 - y0) == 0)
                {
                    while (DeltaX-- != 0)
                    {
                        offset += XDir;
                        DrawWuPixel(offset, BaseColor);
                    }
                    return;
                }

                // vertical special case
                if (DeltaX == 0)
                {
                    do
                    {
                        offset += stride;
                        DrawWuPixel(offset, BaseColor);
                    } while (--DeltaY != 0);
                    return;
                }

                // diagonal special case
                if (DeltaX == DeltaY)
                {
                    do
                    {
                        offset += stride + XDir;
                        DrawWuPixel(offset, BaseColor);
                    } while (--DeltaY != 0);
                    return;
                }

                ErrorAcc = 0;
                IntensityShift = 24;

                if (DeltaY > DeltaX)
                {
                    // Y-major line
                    ErrorAdj = (uint)((((ulong)DeltaX) << 32) / (ulong)DeltaY);
                    while (--DeltaY != 0)
                    {
                        ErrorAccTemp = ErrorAcc;
                        ErrorAcc += ErrorAdj;
                        if (ErrorAcc <= ErrorAccTemp)
                        {
                            offset += XDir;
                        }
                        offset += stride;

                        Weighting = 255 - (ErrorAcc >> IntensityShift);
                        DrawWuPixel(offset, new ColorBgr32(color, (byte)Weighting).RGB);
                        DrawWuPixel(offset + XDir, new ColorBgr32(color, (byte)(255 - Weighting)).RGB);
                    }
                }
                else
                {
                    // X-major line
                    ErrorAdj = (uint)((((ulong)DeltaY) << 32) / (ulong)DeltaX);
                    while (--DeltaX != 0)
                    {
                        ErrorAccTemp = ErrorAcc;
                        ErrorAcc += ErrorAdj;
                        if (ErrorAcc <= ErrorAccTemp)
                        {
                            offset += stride;
                        }
                        offset += XDir;

                        Weighting = 255 - (ErrorAcc >> IntensityShift);
                        DrawWuPixel(offset, new ColorBgr32(color, (byte)Weighting).RGB);
                        DrawWuPixel(offset + stride, new ColorBgr32(color, (byte)(255 - Weighting)).RGB);
                    }
                }

                // draw final pixel
                DrawWuPixel(y1 * stride + x1, BaseColor);
            }
        }

        public void DrawLine(Vec2 a, Vec2 b, Color4 color)
        {
            int x0 = Util.Clamp((int)a.X, 0, RC.FrameBuffer.PixelWidth - 1);
            int y0 = Util.Clamp((int)a.Y, 0, RC.FrameBuffer.PixelHeight - 1);
            int x1 = Util.Clamp((int)b.X, 0, RC.FrameBuffer.PixelWidth - 1);
            int y1 = Util.Clamp((int)b.Y, 0, RC.FrameBuffer.PixelHeight - 1);

            DrawWuLine(x0, y0, x1, y1, new ColorBgr32(color));
        }

        public void Render()
        {
            if (RC == null)
                return;

            cachedPixels = RC.FrameBuffer.Pixels;

            // red horizontal line
            DrawLine(new Vec2(10.0, 10.0), new Vec2((double)RC.FrameBuffer.PixelWidth - 11.0, 10.0), new Color4(255, 255, 0, 0));

            // green vertical line
            DrawLine(new Vec2(10.0, 10.0), new Vec2(10.0, (double)RC.FrameBuffer.PixelHeight - 11.0), new Color4(255, 0, 255, 0));

            // yellow diagonal line
            DrawLine(new Vec2(10.0, 10.0), new Vec2(100.0, 100.0), new Color4(255, 255, 255, 0));

            // red X major line
            DrawLine(new Vec2(10.0, 10.0), new Vec2(300.0, 100.0), new Color4(255, 255, 0, 0));

            // blue Y major line
            DrawLine(new Vec2(10.0, 10.0), new Vec2(100.0, 300.0), new Color4(255, 0, 0, 255));

            //int totalPixels = RC.FrameBuffer.Pixels.Length;
            //int offset = 0;
            //for (int i = 0; i < totalPixels; ++i)
            //{
            //    unchecked
            //    {
            //        cachedPixels[i] = 0x22FF0000;
            //    }
            //}

            // swap buffers
            RC.FrameBuffer.Invalidate();
        }
    }
}
